#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <left4dhooks>

#define PLUGIN_VERSION "1.0"

public Plugin myinfo = 
{
	name = "l4d2_timer_boss_spawner",
	author = "豆瓣酱な",
	description = "每60秒在最前方玩家附近生成Tank和Witch(可配置)",
	version = PLUGIN_VERSION,
	url = "https://gitee.com/sweet-and-sour-bombs"
};

// Cvars
ConVar g_cvEnabled;
ConVar g_cvSpawnInterval;
ConVar g_cvSpawnDistance;
ConVar g_cvMinSpawnDistance;
ConVar g_cvInitialDelay;
ConVar g_cvSpawnBoth;

Handle g_hTimer;
float g_fMapStartPosition[3];
bool g_bGotStartPosition;

public void OnPluginStart()
{
	// 创建Cvars
	g_cvEnabled = CreateConVar("l4d2_boss_spawner_enable", "1", "启用/禁用插件 (0=禁用, 1=启用)", FCVAR_NOTIFY);
	g_cvSpawnInterval = CreateConVar("l4d2_boss_spawn_interval", "60.0", "生成Boss的时间间隔(秒)", FCVAR_NOTIFY);
	g_cvSpawnDistance = CreateConVar("l4d2_boss_spawn_distance", "500.0", "生成点距离玩家的最大距离", FCVAR_NOTIFY);
	g_cvMinSpawnDistance = CreateConVar("l4d2_boss_min_spawn_distance", "300.0", "生成点距离玩家的最小距离", FCVAR_NOTIFY);
	g_cvInitialDelay = CreateConVar("l4d2_boss_initial_delay", "30.0", "回合开始后首次生成的延迟时间(秒)", FCVAR_NOTIFY);
	g_cvSpawnBoth = CreateConVar("l4d2_boss_spawn_both", "1", "是否同时生成Tank和Witch (0=只生成Tank, 1=都生成)", FCVAR_NOTIFY);
	
	// 设置Cvars变化回调
	g_cvEnabled.AddChangeHook(ConVarChanged);
	g_cvSpawnInterval.AddChangeHook(ConVarChanged);
	
	// 注册命令
	RegAdminCmd("sm_boss_spawn_now", Command_SpawnNow, ADMFLAG_SLAY, "立即生成Boss");
	
	HookEvent("round_start", Event_RoundStart);
	HookEvent("round_end", Event_RoundEnd);
	HookEvent("player_spawn", Event_PlayerSpawn);
	
	//AutoExecConfig(true, "l4d2_timer_boss_spawner");
}

public void ConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
	if(convar == g_cvEnabled || convar == g_cvSpawnInterval)
	{
		RestartTimer();
	}
}

void RestartTimer()
{
	if(g_hTimer != null)
	{
		KillTimer(g_hTimer);
		g_hTimer = null;
	}
	
	if(g_cvEnabled.BoolValue)
	{
		float interval = g_cvSpawnInterval.FloatValue;
		g_hTimer = CreateTimer(interval, Timer_SpawnBosses, _, TIMER_REPEAT);
	}
}

public void OnMapStart()
{
	g_bGotStartPosition = false;
	GetMapStartPosition();
}

void GetMapStartPosition()
{
	int entity = -1;
	float pos[3];
	
	while((entity = FindEntityByClassname(entity, "info_player_start")) != -1)
	{
		GetEntPropVector(entity, Prop_Data, "m_vecOrigin", pos);
		g_fMapStartPosition = pos;
		g_bGotStartPosition = true;
		break;
	}
	
	if(!g_bGotStartPosition)
	{
		entity = -1;
		while((entity = FindEntityByClassname(entity, "info_survivor_position")) != -1)
		{
			GetEntPropVector(entity, Prop_Data, "m_vecOrigin", pos);
			g_fMapStartPosition = pos;
			g_bGotStartPosition = true;
			break;
		}
	}
}

public Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast)
{
	int client = GetClientOfUserId(event.GetInt("userid"));
	if(client > 0 && GetClientTeam(client) == 2 && !g_bGotStartPosition)
	{
		GetClientAbsOrigin(client, g_fMapStartPosition);
		g_bGotStartPosition = true;
	}
	return Plugin_Continue;
}

public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast)
{
	RestartTimer();
	
	if(g_cvEnabled.BoolValue)
	{
		float delay = g_cvInitialDelay.FloatValue;
		CreateTimer(delay, Timer_SpawnBosses);
	}
	
	return Plugin_Continue;
}

public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
{
	if(g_hTimer != null)
	{
		KillTimer(g_hTimer);
		g_hTimer = null;
	}
	
	return Plugin_Continue;
}

public Action Command_SpawnNow(int client, int args)
{
	if(!g_cvEnabled.BoolValue)
	{
		ReplyToCommand(client, "[BossSpawner] 插件已禁用");
		return Plugin_Handled;
	}
	
	CreateTimer(0.1, Timer_SpawnBosses);
	ReplyToCommand(client, "[BossSpawner] 已强制生成Boss");
	
	return Plugin_Handled;
}

public Action Timer_SpawnBosses(Handle timer)
{
	if(!g_cvEnabled.BoolValue)
		return Plugin_Stop;
	
	int client = GetFarthestSurvivor();
	if(client == -1) return Plugin_Continue;
	
	float spawnPos[3];
	if(!FindSpawnPosition(client, spawnPos)) return Plugin_Continue;
	
	SpawnBossAtPosition("tank", spawnPos);
	
	if(g_cvSpawnBoth.BoolValue)
	{
		DataPack pack = new DataPack();
		pack.WriteFloat(spawnPos[0]);
		pack.WriteFloat(spawnPos[1]);
		pack.WriteFloat(spawnPos[2]);
		
		CreateTimer(0.2, Timer_SpawnWitch, pack);
	}
	
	return Plugin_Continue;
}

public Action Timer_SpawnWitch(Handle timer, DataPack pack)
{
	pack.Reset();
	float spawnPos[3];
	spawnPos[0] = pack.ReadFloat();
	spawnPos[1] = pack.ReadFloat();
	spawnPos[2] = pack.ReadFloat();
	delete pack;
	
	SpawnBossAtPosition("witch", spawnPos);
	return Plugin_Continue;
}

int GetFarthestSurvivor()
{
	int bestClient = -1;
	float bestDistance = -1.0;
	float distance, origin[3];
	
	if(!g_bGotStartPosition)
	{
		for(int i = 1; i <= MaxClients; i++)
		{
			if(IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i))
			{
				GetClientAbsOrigin(i, g_fMapStartPosition);
				g_bGotStartPosition = true;
				break;
			}
		}
	}
	
	for(int i = 1; i <= MaxClients; i++)
	{
		if(!IsClientInGame(i) || GetClientTeam(i) != 2 || !IsPlayerAlive(i))
			continue;
		
		GetClientAbsOrigin(i, origin);
		distance = GetVectorDistance(g_fMapStartPosition, origin);
		
		if(distance > bestDistance)
		{
			bestDistance = distance;
			bestClient = i;
		}
	}
	
	return bestClient;
}

bool FindSpawnPosition(int client, float spawnPos[3])
{
	if(client == -1) return false;
	
	float clientPos[3];
	GetClientAbsOrigin(client, clientPos);
	
	float spawnDistance = g_cvSpawnDistance.FloatValue;
	
	for(int i = 0; i < 10; i++)
	{
		spawnPos[0] = clientPos[0] + GetRandomFloat(-spawnDistance, spawnDistance);
		spawnPos[1] = clientPos[1] + GetRandomFloat(-spawnDistance, spawnDistance);
		spawnPos[2] = clientPos[2];
		
		if(IsValidSpawnPosition(spawnPos, clientPos))
			return true;
	}
	
	return false;
}

bool IsValidSpawnPosition(const float pos[3], const float clientPos[3])
{
	float minDistance = g_cvMinSpawnDistance.FloatValue;
	if(GetVectorDistance(pos, clientPos) < minDistance)
		return false;
	
	// 移除 TR_PointOutsideWorld 检查，改用更简单的方法
	if(!IsPointInMapBounds(pos))
		return false;
	
	float groundPos[3];
	if(!FindGroundPosition(pos, groundPos))
		return false;
	
	if(!IsPositionWalkable(groundPos))
		return false;
	
	return true;
}

// 替代 TR_PointOutsideWorld 的简单检查
bool IsPointInMapBounds(const float pos[3])
{
	// 简单的边界检查，避免在极端位置生成
	if(pos[0] < -16000.0 || pos[0] > 16000.0 ||
	   pos[1] < -16000.0 || pos[1] > 16000.0 ||
	   pos[2] < -16000.0 || pos[2] > 16000.0)
		return false;
	
	return true;
}

bool FindGroundPosition(const float pos[3], float groundPos[3])
{
	float endPos[3];
	endPos[0] = pos[0];
	endPos[1] = pos[1];
	endPos[2] = pos[2] - 1000.0;
	
	Handle trace = TR_TraceRayFilterEx(pos, endPos, MASK_SOLID_BRUSHONLY, RayType_EndPoint, TraceFilterAll);
	
	if(TR_DidHit(trace))
	{
		TR_GetEndPosition(groundPos, trace);
		delete trace;
		return true;
	}
	
	delete trace;
	return false;
}

bool IsPositionWalkable(const float pos[3])
{
	float mins[3] = {-16.0, -16.0, 0.0};
	float maxs[3] = {16.0, 16.0, 72.0};
	
	Handle trace = TR_TraceHullFilterEx(pos, pos, mins, maxs, MASK_SOLID, TraceFilterAll);
	bool hit = TR_DidHit(trace);
	delete trace;
	
	return !hit;
}

public bool TraceFilterAll(int entity, int contentsMask)
{
	return entity > MaxClients;
}

void SpawnBossAtPosition(const char[] bossType, const float pos[3])
{
	int fakeClient = CreateFakeClient(bossType);
	if(fakeClient > 0)
	{
		TeleportEntity(fakeClient, pos, NULL_VECTOR, NULL_VECTOR);
		CheatCommand(fakeClient, "z_spawn", bossType);
		KickClient(fakeClient);
	}
}

void CheatCommand(int client, const char[] command, const char[] arguments)
{
	int flags = GetCommandFlags(command);
	SetCommandFlags(command, flags & ~FCVAR_CHEAT);
	FakeClientCommand(client, "%s %s", command, arguments);
	SetCommandFlags(command, flags);
}